#!/usr/bin/env python

import re, os, sys

from os3.utils.utils import md5crypt
from os3.types.odict import odict
import args_parser


class LayoutManager ( object ):
	html_modes = {
		"XHTML-STRICT" : '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n<html xmlns="http://www.w3.org/1999/xhtml" lang="%(lang)s" xml:lang="%(lang)s" %(_xmlns)s>',
		"XHTML-TRAN"   : '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n<html xmlns="http://www.w3.org/1999/xhtml" lang="%(lang)s" xml:lang="%(lang)s" %(_xmlns)s>',
		"HTML4-STRICT" : '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n<html lang="%(lang)s" %(_xmlns)s>',
		"HTML4-TRAN"   : '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n<html lang="%(lang)s" %(_xmlns)s>',
		"XHTML-11"     : '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n<html lang="%(lang)s" %(_xmlns)s>',
		"HTML5"	       : '<!DOCTYPE html>\n<html lang="%(lang)s" %(_xmlns)s>',
	}

	def __init__ ( self, liwe ):
		self.liwe = liwe
		self.html_mode = "HTML5"

		self._xmlns = {}
		self.__prepare ()


	def __prepare ( self ):
		self.user_agent = self.liwe.env.get ( "HTTP_USER_AGENT", "X" )
		self.is_ie = "MSIE" in self.user_agent

		# Language of the HTML page
		self.lang = "en"

		# FIXME: da leggere da liwe.cfg non appena funziona
		self._page_dir = self.liwe.cfg.get ( "site.pages", "./pages" )

		self._re_spaces = re.compile ( r"[\n\r\t]" )
		self._re_body = re.compile ( r".*<body[^>]*>(.*)<\/body>.*" )
		self._re_rep  = re.compile (  r"<!-- {{{ REP: (?P<nome>[^ ]*) (.*) -->.*<!-- }}} (?P=nome) -->" )
		self._re_comments = re.compile ( r"<!--.*?-->" )
		self._re_commands = re.compile ( r'(".*?"|\'.*?\')|\W' )

		self._css = odict ()
		self._js  = odict ()
	
		self._css_nocache = odict ()
		self._js_nocache  = odict ()

		self._js_code = []
		self._js_code_tail = []
		self._templates = {}

		self._links = odict ()

		self._cache_dir = None
		self._site_version = self.liwe.cfg.get ( "site.version", 1 )

		self.title = self.liwe.cfg.get ( "title.value", "" )

		self._metas = odict ()
		self._metas [ "resource-type" ] =  "document"

		self._body_classes = []

		self._chrome_frame ()
		self._enable_cache ()
		self._has_redir = False

		self._set_default_values ( "meta", self._metas )
		self._set_default_values ( "xmlns", self._xmlns )

	def _set_default_values ( self, name, dest ):
		metas = self.liwe.cfg.get ( name )
		if not metas: return

		"""
		meta:
			"og:url" : "value"
		"""
		for k,v in metas.iteritems ():
			dest [ k ] = v

	def redirect ( self, url ):
		self._has_redir = True
		self.liwe.pyhp.redirect ( url )

	def _enable_cache ( self ):
		self._cache_enabled = self.liwe.cfg.get ( "site.cache.enabled" )
		self._cache_dir = self.liwe.cfg.get ( "site.cache.dir", "" )
		self._cache_templates = self.liwe.cfg.get ( "site.cache.templates", 0 )
		self._cache_js = self.liwe.cfg.get ( "site.cache.js", 0 )
		self._cache_css = self.liwe.cfg.get ( "site.cache.css", 0 )

		if self._cache_dir and self._cache_enabled:
			if  not os.path.exists ( self._cache_dir ):
				self._cache_enabled = False
				sys.stderr.write ( "WARNING: cache dir: '%s' does not exists\n" % self._cache_dir )
				return

			if not os.access ( self._cache_dir, os.W_OK ):
				self._cache_enabled = False
				sys.stderr.write ( "WARNING: cache dir: '%s' not writeable\n" % self._cache_dir )
				return


	def set_xmlns ( self, name, uri ):
		self._xmlns [ name ] = uri

	def set_meta ( self, name, value ):
		self._metas [ name.lower () ] = unicode ( value ).encode ( 'ascii', 'xmlcharrefreplace' )

	def dump_metas ( self ):
		for k in self._metas.keys (): 
			v = self._metas.get ( k, "" )
			if not v: continue

			v = v.replace ( "\n", " " ).replace ( "\r", " " ).replace ( '"', "'" )
				
			print '<meta name="%s" property="%s" content="%s" />' % ( k, k, v )

		print '<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />'

	def dump_templates ( self ):
		from hashlib import md5
		if not self._cache_templates: return

		buf = []
		for k, v in self._templates.iteritems ():
			buf.append ( "if ( window.%s ) %s.templates = %s;\n" % ( k, k, v ) )

		buf = ''.join ( buf )

		fname = "templates-%s.js" % ( md5 ( buf ).hexdigest () )

		if self._cache_enabled and self._cache_templates:
			full_name = os.path.join ( self._cache_dir, fname )
			if not os.path.exists ( full_name ):
				open ( full_name, "w" ).write ( buf )
			
			print '<script type="text/javascript" src="%s"></script>' % full_name
		else:
			print '<script type="text/javascript">%s</script>' % buf

	def templates_add ( self, name, templates ):
		if not self._cache_templates or not self._cache_enabled: return

		if name not in self._templates:
			self._templates [ name ] = templates
		"""
		if templates: 
			self._templates [ name ] = templates
		"""

	def add_link ( self, rel, type, href, title ):
		self._links [ href ] = { "rel" : rel, "type" : type, "title" : title, "href" : href }

	def add_css ( self, fname, id = '', media = '', ie = None, skip_cache = False ):
		if skip_cache or ie:
			self._css_nocache [ fname ] = { "fname" : fname, "id" : id, "media" : media, "ie" : ie }
		else:
			self._css [ fname ] = { "fname" : fname, "id" : id, "media" : media }

	def add_js ( self, fname, skip_cache = False ):
		if skip_cache:
			dest = self._js_nocache
		else:
			dest = self._js

		if isinstance ( fname, list ):
			for fn in fname:
				dest [ fn ] = fn
		else:
			dest [ fname ] = fname

	def add_js_code ( self, txt, tail = 0 ):
		if not tail:
			self._js_code.append ( txt )
		else:
			self._js_code_tail.insert ( 0, txt )

	def links_refs ( self ):
		for lnk in self._links.values ():
			#lnk [ 'href' ] = self._resolve_ref ( lnk [ 'href' ] )
			res = [ "<link" ]
			for k, v in lnk.iteritems ():
				res.append ( '%s="%s"' % ( k, v ) )

			res.append ( "/>" )

			print ' '.join ( res )

	def css_refs ( self ):
		if self._cache_enabled and self._cache_css:
			self._css_refs_cache ()
		else:
			for css in self._css.values ():
				res = []

				for el in ( "id", "media" ):
					if css.get ( el ):
						res.append ( '%s="%s"' % ( el, css [ el ] ) )

				print '<link rel="stylesheet" href="%s?%s" type="text/css" %s/>' % ( css [ 'fname' ], self._site_version, ' '.join ( res ) )

		for css in self._css_nocache.values ():
			prefix = ''
			suffix = ''
			res = []

			for el in ( "id", "media" ):
				if css.get ( el ):
					res.append ( '%s="%s"' % ( el, css [ el ] ) )

			if css.get ( "ie" ):
				prefix = "<!--[if %s]>" % css [ 'ie' ]
				suffix = "<![endif]-->"

			print '%s<link rel="stylesheet" href="%s?%s" type="text/css" %s/>%s' % ( prefix, css [ 'fname' ], self._site_version, ' '.join ( res ), suffix )

	def _css_refs_cache ( self ):
		def _dump_css_cache ( files ):
			import re
			
			#rep_str = re.compile ( r"url *\( *([^)]*)\)", flags = re.IGNORECASE )
			rep_str = re.compile ( r'url *\([ "]*([A-Za-z0-9/._-]+)[ "]*\)', flags = re.IGNORECASE )

			# Funzione che viene passata a _create_cache
			# per manipolare le righe di un CSS prima che vengano
			# aggiunte alla cache
			#
			# Per ora, si occupa di rimpiazzare url() relativi in assoluti
			def _rep_css_line ( path, line ):
				if line.find ( "url" ) == -1: return line

				# Questa funzione viene chiamata dalla regexp
				# per sostituire il path relativo in assoluto
				def _rep_func ( m ):
					return "url(/%s/%s)" % ( path, m.group ( 1 ) )

				line = rep_str.sub ( _rep_func, line )
				return line

			if files:
				cache = self._create_cache ( files, "css", True, _rep_css_line )
				print '<link rel="stylesheet" href="%s?%s" type="text/css"/>' % ( cache, self._site_version )

		files = []
		for css in self._css.values ():
			has_attrs = False
			res = []

			for el in ( "id", "media" ):
				if css.get ( el ):
					has_attrs = True
					res.append ( '%s="%s"' % ( el, css [ el ] ) )

			if has_attrs:
				_dump_css_cache ( files )
				files = []
			
				print '<link rel="stylesheet" href="%s?%s" type="text/css" %s/>' % ( css [ 'fname' ], self._site_version, ' '.join ( res ) )
			else:
				files.append ( css [ 'fname' ] )

		_dump_css_cache ( files )

	def js_refs ( self ):
		cache_name = self._create_cache ( self._js, ext = "js", enabled = self._cache_js )

		if not cache_name:
			for js in self._js.values ():
				print '<script type="text/javascript" src="%s?%s"></script>' % ( js, self._site_version )
		else:
			print '<script type="text/javascript" src="%s"></script>' % cache_name

		for js in self._js_nocache.values ():
			print '<script type="text/javascript" src="%s?%s"></script>' % ( js, self._site_version )

	def render_page ( self, html ):
		if self._has_redir: return

		#self._chrome_frame ()

		tit = "%s %s %s" % ( self.liwe.cfg.get ( "title.prepend", "" ), self.title, self.liwe.cfg.get ( "title.append", "" ) )

		xmlns = []
		for k, v in self._xmlns.iteritems ():
			xmlns.append ( 'xmlns:%s="%s"' % ( k, v ) )

		print self.html_modes [ self.html_mode ] % { "lang" : self.lang, "_xmlns" : ' '.join ( xmlns ) }
		print "<head><title>%s</title>" % tit
		self.dump_metas ()
		self.links_refs ()
		self.css_refs ()
		self.js_refs ()
		self.dump_templates ()
		self._dump_layman_js ()
		print "</head><body"
		if self._body_classes: print ' class="%s"' % ' '.join ( self._body_classes )
		print ">"
		print html
		print "</body></html>"

	def add_body_class ( self, _class ):
		self._body_classes.append ( _class )

	def _chrome_frame ( self ):
		if int ( self.liwe.cfg.get ( "site.chrome_frame", 0 ) ) and self.is_ie:
			import parts
			self.liwe.pyhp.set_header ( 'X-UA-Compatible', 'chrome=1' )
			self.add_js ( 'http://ajax.googleapis.com/ajax/libs/chrome-frame/1/CFInstall.min.js' )
			parts.add ( self, "ext" )
			parts.add_wwl ( self, "chromeframe" )
			self.add_js_code ( 'chromeframe.init()' )

	def _dump_layman_js ( self ):
		print '<script type="text/javascript" language="JavaScript">'
		print 'liwe.AJAX.url = "/ajax.pyhp";'

		has_init = False

		if self._js_code or self._js_code_tail:
			print "liwe.events.add ( window, 'load', function () {"
			for line in self._js_code:
				print line

			for line in self._js_code_tail:
				print line

			print '} );'
			#print "liwe.events.add ( window, 'load', layman_init );"
			has_init = True

		print '</script>'

		return has_init


	def _create_cache ( self, lst, ext, enabled = 0, rep_func = None ):
		if not self._cache_enabled or not enabled: return ''

		compress = self.liwe.cfg.get ( "site.cache.compress", 0 )

		cache_name = "%s-%s.%s" % ( ext, md5crypt ( '-'.join ( lst ) + str ( self._site_version ) + str ( compress ) ), ext )

		full_name = os.path.join ( self._cache_dir, cache_name )
		if os.path.isfile ( full_name ): return full_name

		try:

			txt = []	# Output file in a list

			for f in lst:
				if f [ 0 ] == '/': f = f [ 1 : ]
				base_dir = os.path.dirname ( f )

				try:
					txt.append ( "/* %s */" % f )
					#fout.write ( "/* %s */\n" % f )
					for line in open ( f ):
						if rep_func:
							line = rep_func ( base_dir, line )

						txt.append ( line )
						#fout.write ( line )
				except:
					sys.stderr.write ( "ERROR: could not read: %s\n" % f )

			txt = ''.join ( txt )

			if compress:
				if ext == 'js':
					try:
						import jsmin
						txt = jsmin.jsmin ( txt )
					except:
						pass
				elif ext == 'css':
					try:
						import os3.utils.cssmin as cssmin
						txt = cssmin.cssmin ( txt )
					except:
						pass

			open ( full_name, "w" ).write ( txt )

			return full_name
		except:
			return ''

	# {{{ load_page ( page_name, block_name = False )
	def load_page ( self, page_name, block_name = False ):
		fname = os.path.join ( self._page_dir, page_name + ".html" )

		if os.path.isfile ( fname ):
			txt = open ( fname ).read ()
			txt = self._apply_regex ( txt, block_name )
		else:
			txt = "Page not found: %s" % fname

		return self._parse_data ( txt )
	# }}}
	# {{{ execute ( command )
	def execute ( self, command, kargs ):
		cmd = "%s.%s" % ( command [ 0 ], command [ 1 ] )
		res = self.liwe.invoke ( cmd, * command [ 2 : ], ** kargs )
		if '_txt' in res:
			txt = res [ '_txt' ]
		else:
			txt = "Layout Manager: Error invoking: %s (%s / %s)" % ( cmd, self.liwe.err_code, self.liwe.err_descr )
	
		return txt
	# }}}

	# {{{ _parse_data ( txt )
	def _parse_data ( self, txt ):
		def _remap ( txt ):
			return self.liwe.pyhp.session.data.get ( txt [ 1 : ], txt )

		res = ''
		start_pos = 0

		x = txt.find ( "<?fab" )
		while x != -1:
			res += txt [ start_pos : x ]
			start_pos = x + 5

			x = txt.find ( "?>", start_pos )
			if x == -1:
				sys.stderr.write ( "ERROR: missing end tag in template. Last valid pos: %d" % start_pos )
				break

			sub = txt [ start_pos : x ]
			cmds, kargs = args_parser.parse ( sub, _remap )
			subres = self.execute ( cmds, kargs )
			if isinstance ( subres, basestring ):
				res += subres.encode ( 'utf-8' )
			else:
				sys.stderr.write ( "LayoutManager: ERROR: fab result is not a string for %s\n" % cmds )

			start_pos = x + 2
			x = txt.find ( "<?fab", start_pos )

		res += txt [ start_pos : ]

		return res
	# }}}
	# {{{ _split_command ( txt )
	def _split_command ( self, txt ):
		spl = [ self._unquote ( x ) for x in self._re_commands.split ( txt ) if x  ]
		return spl
	# }}}
	# {{{ _unquote ( x )
	def _unquote ( self, x ):
		if x [ 0 ] in ( "'", '"' ):
			return x [ 1 : -1 ]
		return x
	# }}}



	# {{{ _apply_regex ( txt, block_name )
	def _apply_regex ( self, txt, block_name ):
		txt = self._re_spaces.sub ( " ", txt )
		txt = self._re_body.sub ( "\\1", txt )

		if block_name:
			if txt.find ( "{{{ BLK: %s" % block_name ) == -1:
				return '<strong style="color: red">Could not find: %s</strong>' % block_name

			regexp = re.compile ( ".*<!-- {{{ BLK: %s -->(.*)<!-- }}} %s -->(.*)$" % ( block_name, block_name ) )
			txt = regexp.sub ( "\\1", txt )

		txt = self._re_rep.sub ( "\\2", txt )
		txt = self._re_comments.sub ( "", txt )

		return txt.strip ()
	# }}}

		



if __name__ == '__main__':
	m = LayoutManager ( None )
	print m.load_page ( "work/test" )
	print m.load_page ( "work/test", "test" )
	print m.load_page ( "work/test", "pizza" )
	
